// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

// TODO(johnniwinther): Remove this library when all constant constructors are
// computed during resolution.
library dart2js.constants.constant_constructors;

import '../common.dart';
import '../elements/elements.dart';
import '../elements/operators.dart';
import '../elements/resolution_types.dart';
import '../resolution/semantic_visitor.dart';
import '../resolution/send_resolver.dart' show DeclarationResolverMixin;
import '../resolution/send_structure.dart';
import '../resolution/tree_elements.dart' show TreeElements;
import '../tree/tree.dart';
import '../universe/call_structure.dart' show CallStructure;
import 'constructors.dart';
import 'expressions.dart';

ConstantConstructor computeConstantConstructor(ResolvedAst resolvedAst) {
  ConstantConstructorComputer visitor =
      new ConstantConstructorComputer(resolvedAst.elements);
  return resolvedAst.node.accept(visitor);
}

class ConstantConstructorComputer extends SemanticVisitor
    with
        SemanticDeclarationResolvedMixin,
        DeclarationResolverMixin,
        GetBulkMixin,
        SetBulkMixin,
        ErrorBulkMixin,
        InvokeBulkMixin,
        IndexSetBulkMixin,
        CompoundBulkMixin,
        SetIfNullBulkMixin,
        UnaryBulkMixin,
        BaseBulkMixin,
        BinaryBulkMixin,
        PrefixBulkMixin,
        PostfixBulkMixin,
        NewBulkMixin,
        InitializerBulkMixin,
        FunctionBulkMixin,
        VariableBulkMixin
    implements SemanticDeclarationVisitor, SemanticSendVisitor {
  final Map<FieldElement, ConstantExpression> fieldMap =
      <FieldElement, ConstantExpression>{};
  final Map<dynamic /*int|String*/, ConstantExpression> defaultValues =
      <dynamic /*int|String*/, ConstantExpression>{};

  ConstantConstructorComputer(TreeElements elements) : super(elements);

  SemanticDeclarationVisitor get declVisitor => this;

  SemanticSendVisitor get sendVisitor => this;

  ClassElement get currentClass => currentConstructor.enclosingClass;

  ConstructorElement get currentConstructor => elements.analyzedElement;

  apply(Node node, [_]) => node.accept(this);

  visitNode(Node node) {
    internalError(node, 'Unhandled node $node: ${node.toDebugString()}');
  }

  @override
  bulkHandleNode(Node node, String template, _) {
    internalError(node, template.replaceFirst('#', '$node'));
  }

  internalError(Spannable node, String message) {
    throw new UnsupportedError(message);
  }

  ConstantConstructor visitGenerativeConstructorDeclaration(
      FunctionExpression node,
      ConstructorElement constructor,
      NodeList parameters,
      NodeList initializers,
      Node body,
      _) {
    applyParameters(parameters, _);
    ConstructedConstantExpression constructorInvocation =
        applyInitializers(node, _);
    constructor.enclosingClass.forEachInstanceField((_, FieldElement field) {
      if (!fieldMap.containsKey(field)) {
        fieldMap[field] = field.constant;
      }
    });
    return new GenerativeConstantConstructor(
        currentClass.thisType,
        defaultValues,
        fieldMap,
        const <AssertConstantExpression>[],
        constructorInvocation);
  }

  ConstantConstructor visitRedirectingGenerativeConstructorDeclaration(
      FunctionExpression node,
      ConstructorElement constructor,
      NodeList parameters,
      NodeList initializers,
      _) {
    applyParameters(parameters, _);
    ConstructedConstantExpression constructorInvocation =
        applyInitializers(node, _);
    return new RedirectingGenerativeConstantConstructor(
        defaultValues, constructorInvocation);
  }

  ConstantConstructor visitRedirectingFactoryConstructorDeclaration(
      FunctionExpression node,
      ConstructorElement constructor,
      NodeList parameters,
      ResolutionInterfaceType redirectionType,
      ConstructorElement redirectionTarget,
      _) {
    List<String> argumentNames = [];
    List<ConstantExpression> arguments = [];
    int index = 0;
    for (ParameterElement parameter in constructor.parameters) {
      if (parameter.isNamed) {
        String name = parameter.name;
        argumentNames.add(name);
        arguments.add(new NamedArgumentReference(name));
      } else {
        arguments.add(new PositionalArgumentReference(index));
      }
      index++;
    }
    CallStructure callStructure = new CallStructure(index, argumentNames);

    return new RedirectingFactoryConstantConstructor(
        new ConstructedConstantExpression(
            redirectionType, redirectionTarget, callStructure, arguments));
  }

  @override
  visitFactoryConstructorDeclaration(FunctionExpression node,
      ConstructorElement constructor, NodeList parameters, Node body, _) {
    // TODO(johnniwinther): Handle constant constructors with errors.
    internalError(node, "Factory constructor cannot be constant: $node.");
  }

  applyParameters(NodeList parameters, _) {
    computeParameterStructures(parameters).forEach((s) => s.dispatch(this, _));
  }

  visitParameterDeclaration(VariableDefinitions node, Node definition,
      ParameterElement parameter, int index, _) {
    // Do nothing.
  }

  visitOptionalParameterDeclaration(
      VariableDefinitions node,
      Node definition,
      ParameterElement parameter,
      ConstantExpression defaultValue,
      int index,
      _) {
    assert(defaultValue != null, failedAt(node));
    defaultValues[index] = defaultValue;
  }

  visitNamedParameterDeclaration(VariableDefinitions node, Node definition,
      ParameterElement parameter, ConstantExpression defaultValue, _) {
    assert(defaultValue != null, failedAt(node));
    String name = parameter.name;
    defaultValues[name] = defaultValue;
  }

  visitInitializingFormalDeclaration(VariableDefinitions node, Node definition,
      InitializingFormalElement parameter, int index, _) {
    fieldMap[parameter.fieldElement] = new PositionalArgumentReference(index);
  }

  visitOptionalInitializingFormalDeclaration(
      VariableDefinitions node,
      Node definition,
      InitializingFormalElement parameter,
      ConstantExpression defaultValue,
      int index,
      _) {
    assert(defaultValue != null, failedAt(node));
    defaultValues[index] = defaultValue;
    fieldMap[parameter.fieldElement] = new PositionalArgumentReference(index);
  }

  visitNamedInitializingFormalDeclaration(
      VariableDefinitions node,
      Node definition,
      InitializingFormalElement parameter,
      ConstantExpression defaultValue,
      _) {
    assert(defaultValue != null, failedAt(node));
    String name = parameter.name;
    defaultValues[name] = defaultValue;
    fieldMap[parameter.fieldElement] = new NamedArgumentReference(name);
  }

  /// Apply this visitor to the constructor [initializers].
  ConstructedConstantExpression applyInitializers(
      FunctionExpression constructor, _) {
    ConstructedConstantExpression constructorInvocation;
    InitializersStructure initializers =
        computeInitializersStructure(constructor);
    for (InitializerStructure structure in initializers.initializers) {
      if (structure.isConstructorInvoke) {
        constructorInvocation = structure.dispatch(this, _);
      } else {
        structure.dispatch(this, _);
      }
    }
    return constructorInvocation;
  }

  visitFieldInitializer(SendSet node, FieldElement field, Node initializer, _) {
    fieldMap[field] = apply(initializer);
  }

  visitParameterGet(Send node, ParameterElement parameter, _) {
    if (parameter.isNamed) {
      return new NamedArgumentReference(parameter.name);
    } else {
      return new PositionalArgumentReference(
          parameter.functionDeclaration.parameters.indexOf(parameter));
    }
  }

  ConstructedConstantExpression visitSuperConstructorInvoke(
      Send node,
      ConstructorElement superConstructor,
      ResolutionInterfaceType type,
      NodeList arguments,
      CallStructure callStructure,
      _) {
    List<ConstantExpression> argumentExpression =
        arguments.nodes.map((a) => apply(a)).toList();
    return new ConstructedConstantExpression(
        type, superConstructor, callStructure, argumentExpression);
  }

  ConstructedConstantExpression visitImplicitSuperConstructorInvoke(
      FunctionExpression node,
      ConstructorElement superConstructor,
      ResolutionInterfaceType type,
      _) {
    return new ConstructedConstantExpression(type, superConstructor,
        CallStructure.NO_ARGS, const <ConstantExpression>[]);
  }

  ConstructedConstantExpression visitThisConstructorInvoke(
      Send node,
      ConstructorElement thisConstructor,
      NodeList arguments,
      CallStructure callStructure,
      _) {
    List<ConstantExpression> argumentExpression =
        arguments.nodes.map((a) => apply(a)).toList();
    return new ConstructedConstantExpression(currentClass.thisType,
        thisConstructor, callStructure, argumentExpression);
  }

  @override
  ConstantExpression visitBinary(
      Send node, Node left, BinaryOperator operator, Node right, _) {
    return new BinaryConstantExpression(apply(left), operator, apply(right));
  }

  @override
  ConstantExpression visitEquals(Send node, Node left, Node right, _) {
    return new BinaryConstantExpression(
        apply(left), BinaryOperator.EQ, apply(right));
  }

  @override
  ConstantExpression visitNotEquals(Send node, Node left, Node right, _) {
    return new BinaryConstantExpression(
        apply(left), BinaryOperator.NOT_EQ, apply(right));
  }

  @override
  ConstantExpression visitUnary(
      Send node, UnaryOperator operator, Node expression, _) {
    return new UnaryConstantExpression(operator, apply(expression));
  }

  @override
  ConstantExpression visitStaticFieldGet(Send node, FieldElement field, _) {
    return new FieldConstantExpression(field);
  }

  @override
  ConstantExpression visitTopLevelFunctionGet(
      Send node, MethodElement method, _) {
    return new FunctionConstantExpression(method, method.type);
  }

  @override
  ConstantExpression visitTopLevelFieldGet(Send node, FieldElement field, _) {
    return new FieldConstantExpression(field);
  }

  @override
  ConstantExpression visitLiteralInt(LiteralInt node) {
    return new IntConstantExpression(node.value);
  }

  @override
  ConstantExpression visitLiteralDouble(LiteralDouble node) {
    return new DoubleConstantExpression(node.value);
  }

  @override
  ConstantExpression visitLiteralBool(LiteralBool node) {
    return new BoolConstantExpression(node.value);
  }

  @override
  ConstantExpression visitLiteralNull(LiteralNull node) {
    return new NullConstantExpression();
  }

  @override
  ConstantExpression visitLiteralString(LiteralString node) {
    return new StringConstantExpression(node.dartString.slowToString());
  }

  @override
  ConstantExpression visitConditional(Conditional node) {
    return new ConditionalConstantExpression(apply(node.condition),
        apply(node.thenExpression), apply(node.elseExpression));
  }

  @override
  ConstantExpression visitParenthesizedExpression(
      ParenthesizedExpression node) {
    return apply(node.expression);
  }

  @override
  ConstantExpression visitTopLevelFunctionInvoke(
      Send node,
      MethodElement function,
      NodeList arguments,
      CallStructure callStructure,
      _) {
    if (function.name != 'identical' || !function.library.isDartCore) {
      throw new UnsupportedError("Unexpected function call: $function");
    }
    return new IdenticalConstantExpression(
        apply(arguments.nodes.head), apply(arguments.nodes.tail.head));
  }

  @override
  ConstantExpression visitNamedArgument(NamedArgument node) {
    return apply(node.expression);
  }

  @override
  ConstantExpression visitIfNull(Send node, Node left, Node right, _) {
    return new BinaryConstantExpression(
        apply(left), BinaryOperator.IF_NULL, apply(right));
  }
}
